package com.jstarcraft.ai.jsat.utils;

/**
 *
 * @author Edward Raff
 */
public class StringUtils {
    public static int parseInt(CharSequence s, int start, int end, int radix) {
        boolean negative = false;
        int val = 0;

        for (int i = start; i < end; i++) {
            char c = s.charAt(i);
            if (c == '-')
                if (i == start)
                    negative = true;
                else
                    throw new NumberFormatException("Negative sign did not occur at begining of sequence");
            else if (c == '+')
                if (i == start)
                    negative = false;// do nothing really
                else
                    throw new NumberFormatException("Positive sign did not occur at begining of sequence");
            else {
                int digit = Character.digit(c, radix);
                if (digit < 0)
                    throw new NumberFormatException("Non digit character '" + c + "' encountered");
                val *= radix;
                val += digit;
            }
        }
        if (negative)
            return -val;
        else
            return val;
    }

    public static int parseInt(CharSequence s, int start, int end) {
        return parseInt(s, start, end, 10);
    }

    // Float/Double to String follows algo laid out here
    // http://krashan.ppa.pl/articles/stringtofloat/

    private enum States {
        /**
         * Either '+' or '-', or not sign present
         */
        SIGN,
        /**
         * skipping leading zeros in the integer part of mantissa
         */
        LEADING_ZEROS_MANTISSA,
        /**
         * reading leading zeros in the fractional part of mantissa.
         */
        LEADING_ZEROS_FRAC,
        /**
         * reading integer part of mantissa
         */
        MANTISSA_INT_PART,

        /**
         * reading fractional part of mantissa.
         */
        MANTISSA_FRAC_PART,

        /**
         * reading sign of exponent
         */
        EXPO_SIGN,

        EXPO_LEADING_ZERO,

        /**
         * reading exponent digits
         */
        EXPO,
    }

    public static double parseDouble(CharSequence s, int start, int end) {
        // hack check for NaN at the start
        if ((end - start) == 3 && s.length() >= end && s.charAt(start) == 'N')
            if (s.subSequence(start, end).toString().equals("NaN"))
                return Double.NaN;
        States state = States.SIGN;
        int pos = start;

        int sign = 1;
        long mantissa = 0;
        /**
         * Mantissa can only be incremented 18 times, then any more will overflow
         * (2^63-1 ~= 9.2* 10^18
         */
        byte mantisaIncrements = 0;
        int implicitExponent = 0;
        // used for (val)e(val) case
        int expoSign = 1;
        int explicitExponent = 0;

        while (pos < end)// run the state machine
        {
            char c = s.charAt(pos);
            switch (state) {
            case SIGN:
                if (c == '-') {
                    sign = -1;
                    pos++;
                } else if (c == '+')
                    pos++;
                else if (!Character.isDigit(c))// not a '-', '+', or digit, so error
                    throw new NumberFormatException();
                state = States.LEADING_ZEROS_MANTISSA;
                continue;
            case LEADING_ZEROS_MANTISSA:
                if (c == '0')
                    pos++;
                else if (c == '.') {
                    pos++;
                    state = States.LEADING_ZEROS_FRAC;
                } else if (Character.isDigit(c))
                    state = States.MANTISSA_INT_PART;
                else if (c == 'e' || c == 'E')// could be something like +0e0
                    state = States.MANTISSA_FRAC_PART;// this is where that case is handeled
                else
                    throw new NumberFormatException();
                continue;
            case LEADING_ZEROS_FRAC:
                if (c == '0') {
                    pos++;
                    implicitExponent--;
                } else if (Character.isDigit(c))
                    state = States.MANTISSA_FRAC_PART;
                else if (c == 'e' || c == 'E')// could be something like +0.000e0
                    state = States.MANTISSA_FRAC_PART;// this is where that case is handeled
                else
                    throw new NumberFormatException();
                continue;
            case MANTISSA_INT_PART:
                if (c == '.') {
                    pos++;
                    state = States.MANTISSA_FRAC_PART;
                } else if (Character.isDigit(c)) {
                    if (mantisaIncrements < 18) {
                        mantissa = mantissa * 10 + Character.digit(c, 10);
                        mantisaIncrements++;
                    } else// we are going to lose these, compencate with an implicit *= 10
                        implicitExponent++;
                    pos++;
                } else
                    state = States.MANTISSA_FRAC_PART;
                // if we hit a invalid char it will get erred on in FRAC_PART
                continue;
            case MANTISSA_FRAC_PART:
                if (Character.isDigit(c)) {
                    if (mantisaIncrements < 18) {
                        mantissa = mantissa * 10 + Character.digit(c, 10);
                        implicitExponent--;
                        mantisaIncrements++;
                    } else// we are going to lose these
                    {
                        // we would have incresed the implicit exponent
                        // but we would have subtracted if we could
                        // so do nothing
                    }
                    pos++;
                } else if (c == 'e' || c == 'E') {
                    pos++;
                    state = States.EXPO_SIGN;
                } else
                    throw new NumberFormatException();
                continue;
            case EXPO_SIGN:
                if (c == '-') {
                    expoSign = -1;
                    pos++;
                } else if (c == '+')
                    pos++;
                else if (!Character.isDigit(c))// not a '-', '+', or digit, so error
                    throw new NumberFormatException();
                state = States.EXPO_LEADING_ZERO;
                continue;
            case EXPO_LEADING_ZERO:
                if (c == '0') {
                    pos++;
                } else if (Character.isDigit(c))
                    state = States.EXPO;
                else
                    throw new NumberFormatException();
                continue;
            case EXPO:
                if (Character.isDigit(c)) {
                    explicitExponent = explicitExponent * 10 + Character.digit(c, 10);
                    pos++;
                } else
                    throw new NumberFormatException();
                continue;
            }
        }

        int finalExpo = expoSign * explicitExponent + implicitExponent;
        if (mantissa == 0)// easiest case!
            if (sign == -1)
                return -0.0;
            else
                return 0.0;
        if (finalExpo == 0)// easy case!
            return sign * mantissa;

        return sign * (mantissa * Math.pow(10, finalExpo));
    }
}
